Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use projected token volume for hostNetwork pods. #428

Merged
merged 1 commit into from
Feb 5, 2025

Conversation

siyanshen
Copy link
Collaborator

@siyanshen siyanshen commented Jan 14, 2025

Why we need this change

Before this change, GCSFuse CSI driver does not support pods with hostNetwork:true. This is because the gcsfuse process runs as part of a sidecar container that is injected into the user pod. GCSFuse uses the ADC workflow to fetch the necessary token to access a GCS bucket. However, with hostNetwork enabled, GKE metadata server cannot intercept the token requests for GET /computeMetadata/v1/instance/service-accounts/default/token endpoint.

What is in this change

  1. For HostNetwork=true user pods, GCSFuse CSI webhook injects a projected SA token volume to the user pod.
  2. gke-gcsfuse-sidecar container prepares a unix domain socket and starts a handler to serve requests on this token; invoke gcsfuse with a config option to point to the socket gcs-auth:token-url:<path to the token>
  3. Token request handler in the gke-gcsfuse-sidecar handles token request from GCSFuse.

Local set up and testing

Test cases covered

  1. pod with hostNetwork=true: can I/O GCS bucket.
  2. pod with hostNetwork=false: is not affected and can I/O GCS bucket.
  3. pod with hostNetwork=true, with GCS bucket A mounted as ephemeral storage and Gcs bucket B mounted as PV: can access bucket A & B without issue.

Setup

  1. Create a cluster with workload identity pool enabled & GCSFuse disabled.
  2. Grant GCS permissions to an SA
kubectl create namespace <ns>
kubectl create serviceaccount <test-ksa-ns> \
    --namespace <ns>

gcloud storage buckets add-iam-policy-binding gs://<bucket> \
    --member "principal://iam.googleapis.com/projects/<project-number>/locations/global/workloadIdentityPools/<project-id>.svc.id.goog/subject/ns/<ns>/sa/<test-ksa-ns>" \
    --role "roles/storage.objectUser"
  1. Build and install your GCSFuse image
make build-image-and-push-multi-arch REGISTRY=<your-registry> STAGINGVERSION=<your-dev-tag>
make install REGISTRY=<your-registry> PROJECT=<project-id> STAGINGVERSION=<your-dev-tag>
  1. Create a user pod with hostNetwork=true, GCS bucket mounted as a volume, and service account set as the one you created in step 2. Check I/O to your bucket from a container in your pod.

@siyanshen siyanshen force-pushed the hostnetwork branch 7 times, most recently from 3b2b99f to 557c193 Compare January 15, 2025 23:56
@siyanshen siyanshen requested review from hime, saikat-royc, mattcary, msau42, kislaykishore and Tulsishah and removed request for saikat-royc January 16, 2025 00:32
deploy/base/webhook/deployment.yaml Outdated Show resolved Hide resolved
cmd/sidecar_mounter/main.go Outdated Show resolved Hide resolved
pkg/sidecar_mounter/sidecar_mounter_config.go Outdated Show resolved Hide resolved
cmd/sidecar_mounter/main.go Outdated Show resolved Hide resolved
@siyanshen siyanshen force-pushed the hostnetwork branch 2 times, most recently from 9afba55 to 2f4c6e4 Compare January 23, 2025 00:47
@siyanshen siyanshen removed the request for review from msau42 January 27, 2025 17:45
cmd/webhook/main.go Outdated Show resolved Hide resolved
pkg/webhook/sidecar_spec.go Outdated Show resolved Hide resolved
pkg/webhook/sidecar_spec.go Outdated Show resolved Hide resolved
pkg/sidecar_mounter/sidecar_mounter.go Outdated Show resolved Hide resolved
pkg/sidecar_mounter/sidecar_mounter.go Outdated Show resolved Hide resolved
pkg/sidecar_mounter/sidecar_mounter.go Outdated Show resolved Hide resolved
pkg/sidecar_mounter/token_manager.go Outdated Show resolved Hide resolved
pkg/sidecar_mounter/token_manager.go Outdated Show resolved Hide resolved
cmd/webhook/main.go Outdated Show resolved Hide resolved
pkg/sidecar_mounter/sidecar_mounter.go Outdated Show resolved Hide resolved
pkg/sidecar_mounter/token_source.go Outdated Show resolved Hide resolved
@siyanshen siyanshen force-pushed the hostnetwork branch 6 times, most recently from a4c50b1 to f2a2df7 Compare January 31, 2025 19:19
cmd/sidecar_mounter/main.go Outdated Show resolved Hide resolved
@@ -34,6 +34,7 @@ import (
)

var (
hostNetwork = flag.Bool("host-network", false, "pod hostNetwork configuration")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this goes inside mounter.Mount, do we need this flag? I think we pass (or can pass) hostNetwork flag through mountConfig.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can simplify the sidecar injection logic too.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We still need this to pass to mount config

Copy link
Collaborator

@hime hime Feb 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are using the new HostNetwork flag to:

  1. set mc.HostNetwork
  2. setup gcs-auth:token-url:...

To avoid creating flags, we can determine if the Pod supports this feature on gcs-fuse-csi-driver container by checking if hostNetwork is enabled on the pod and the volume we need is injected. We have a pod informer that has that information.

pod, err := s.k8sClients.GetPod(vc[VolumeContextKeyPodNamespace], vc[VolumeContextKeyPodName])

We can then pass the mountConfig from gcs-fuse-csi-driver -> gke-gcsfuse-sidecar

driver sending mc to sidecar:

mc := sidecarmounter.MountConfig{

sidecar receiving mc from driver:

if err := json.Unmarshal(msg, &mc); err != nil {

After that, we can process mc to set the configmap with the uds path

func (mc *MountConfig) prepareMountArgs() {

Copy link
Collaborator Author

@siyanshen siyanshen Feb 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey Jaime, thanks for the suggestion. Somehow the resulting pod of k8sclients.GetPod(...) does not persist the HostNetwork value. It is "false" despite my test pod setting. It might be worthwhile to derive the HostNetwork from pod.Annotations["kubectl.kubernetes.io/last-applied-configuration"], but it might be very messy.

This is the last-applied annotation.
{"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{"gke-gcsfuse/volumes":"true"},"name":"test-pv-pod-hnw","namespace":"ns1"},"spec":{"containers":[{"command":["sleep","3600"],"image":"busybox","name":"busybox","volumeMounts":[{"mountPath":"/data","name":"gcp-cloud-storage-pvc"},{"mountPath":"/dataEph","name":"gcs-fuse-csi-ephemeral"}]}],"hostNetwork":true,"serviceAccountName":"test-ksa-ns1","volumes":[{"name":"gcp-cloud-storage-pvc","persistentVolumeClaim":{"claimName":"gcp-cloud-storage-csi-static-pvc"}},{"csi":{"driver":"gcsfuse.csi.storage.gke.io","volumeAttributes":{"bucketName":"test-wi-host-network-2","mountOptions":"implicit-dirs"}},"name":"gcs-fuse-csi-ephemeral"}]}}

So far the cleanest and safest solution is passing the bool flag from webhook. What do you think? Let me know.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Synced with Jaime offline. PodSpec was filtered in this PR: #413

This approach proves to be unfeasible even after adding back hostNetwork in podSpec. In csi driver, the msg is sent to the listner in the csi path, that looks like "/var/lib/kubelet/pods/c9a5236b-cf41-49c0-bcfe-f61988440b8f/volumes/kubernetes.io~csi/gcs-fuse-csi-ephemeral/mount". The msg received in sidecar mounter is received in the path of the volume socket: "connecting to socket "/gcsfuse-tmp/.volumes/gcs-fuse-csi-ephemeral/socket". They are not the same msg.

Will stick to the original design.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@siyanshen Do we know how all the other options are making their way from the node-server to the sidecar? Regarding the different paths being used, I believe we are using a symbolic link for socket communication. Could you clarify this so going forward we follow a different approach then?

// Create socket base path.
// Need to create symbolic link of emptyDirBasePath to socketBasePath,
// because the socket absolute path is longer than 104 characters,
// which will cause "bind: invalid argument" errors.
socketBasePath := util.GetSocketBasePath(target, m.fuseSocketDir)

@kislaykishore
Copy link

/LGTM

@siyanshen siyanshen merged commit eeeb147 into GoogleCloudPlatform:main Feb 5, 2025
8 checks passed
@hime
Copy link
Collaborator

hime commented Feb 6, 2025

lgtm

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Approved enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants